package com.gcloud.controller.network.service.impl;

import com.gcloud.common.util.StringUtils;
import com.gcloud.controller.ResourceProviders;
import com.gcloud.controller.ResourceStates;
import com.gcloud.controller.compute.dao.InstanceDao;
import com.gcloud.controller.compute.entity.VmInstance;
import com.gcloud.controller.network.dao.FloatingIpDao;
import com.gcloud.controller.network.dao.PortDao;
import com.gcloud.controller.network.dao.QosFipPolicyBindingDao;
import com.gcloud.controller.network.entity.FloatingIp;
import com.gcloud.controller.network.entity.Port;
import com.gcloud.controller.network.entity.QosFipPolicyBinding;
import com.gcloud.controller.network.entity.QosPolicy;
import com.gcloud.controller.network.model.AllocateEipAddressResponse;
import com.gcloud.controller.network.model.AssociateEipAddressParams;
import com.gcloud.controller.network.model.ModifyEipAddressAttributeParams;
import com.gcloud.controller.network.provider.IFloatingIpProvider;
import com.gcloud.controller.network.service.IFloatingIpService;
import com.gcloud.controller.network.service.IPortService;
import com.gcloud.controller.network.service.IQosFipPolicyBindingService;
import com.gcloud.controller.network.service.IQosPolicyService;
import com.gcloud.core.exception.GCloudException;
import com.gcloud.core.simpleflow.Flow;
import com.gcloud.core.simpleflow.NoRollbackFlow;
import com.gcloud.core.simpleflow.SimpleFlowChain;
import com.gcloud.framework.db.PageResult;
import com.gcloud.header.api.model.CurrentUser;
import com.gcloud.header.compute.enums.DeviceType;
import com.gcloud.header.enums.ProviderType;
import com.gcloud.header.enums.ResourceType;
import com.gcloud.header.network.enums.FloatingIpStatus;
import com.gcloud.header.network.model.DetailEipAddressResponse;
import com.gcloud.header.network.model.EipAddressSetType;
import com.gcloud.header.network.msg.api.ApiDetailEipAddressMsg;
import com.gcloud.header.network.msg.api.ApiEipAddressStatisticsReplyMsg;
import com.gcloud.header.network.msg.api.DescribeEipAddressesMsg;
import lombok.extern.slf4j.Slf4j;
import org.openstack4j.model.network.State;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
@Transactional(propagation = Propagation.REQUIRED)
public class FloatingIpServiceImpl implements IFloatingIpService {
    @Autowired
    FloatingIpDao floatingipDao;

    @Autowired
    PortDao portDao;

    @Autowired
    InstanceDao vmInstanceDao;
    
    @Autowired
    private IQosPolicyService qosPolicyService;

    @Autowired
    private IQosFipPolicyBindingService qosFipPolicyBindingService;

    @Autowired
    private QosFipPolicyBindingDao qosFipPolicyBindingDao;

    @Autowired
    private IPortService portService;

    @Override
    public PageResult<EipAddressSetType> describeEipAddresses(DescribeEipAddressesMsg param) {
    	PageResult<EipAddressSetType> pages = floatingipDao.getByPage(param);
    	pages.getList().forEach(f -> f.setCnStatus(FloatingIpStatus.getCnName(f.getStatus())));
        return pages;
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public AllocateEipAddressResponse allocateEipAddress(String networkId, Integer bandwidth, String regionId, CurrentUser currentUser) {

        IFloatingIpProvider floatingIpProvider = getProviderOrDefault();
        SimpleFlowChain<AllocateEipAddressResponse, AllocateEipAddressResponse> chain = new SimpleFlowChain<>("create eip");
        chain.then(new Flow<AllocateEipAddressResponse>() {
            @Override
            public void run(SimpleFlowChain chain, AllocateEipAddressResponse data) {
                AllocateEipAddressResponse response = floatingIpProvider.allocateEipAddress(networkId, regionId, currentUser);
                chain.data(response);
                chain.setResult(response);
                chain.next();
            }

            @Override
            public void rollback(SimpleFlowChain chain, AllocateEipAddressResponse data) {
                releaseEipAddress(data.getAllocationId());
                chain.rollback();
            }
        }).then(new NoRollbackFlow<AllocateEipAddressResponse>() {
            //回滚删除eip的时候会删除
            @Override
            public void run(SimpleFlowChain chain, AllocateEipAddressResponse data) {
                if(bandwidth != null && bandwidth > 0){
                    updateFipQosLimit(data.getAllocationId(), bandwidth * 1024, bandwidth * 1024);
                }
            }
        }).start();

        if(StringUtils.isNotBlank(chain.getErrorCode())){
            throw new GCloudException(chain.getErrorCode());
        }

        return chain.getResult();
    }

    @Override
    public void associateEipAddress(AssociateEipAddressParams params) {
        FloatingIp floatingip = floatingipDao.getById(params.getAllocationId());
        if (floatingip == null) {
            throw new GCloudException("0050204::弹性公网ip不存在");
        }
        if(!params.getInstanceType().toLowerCase().equals(DeviceType.NETCARD.value())){
        	throw new GCloudException("0050206::实例类型只支持网卡");
        }
        Port netcard = portDao.getById(params.getInstanceId());
        if (netcard == null) {
            throw new GCloudException("0050205::网卡不存在");
        }
        /*VmInstance instance = vmInstanceDao.getById(params.getInstanceId());
        if (instance == null) {
            throw new GCloudException("0050206::云服务器不存在");
        }*/
        this.checkAndGetProvider(floatingip.getProvider()).associateEipAddress(floatingip.getProviderRefId(), netcard.getProviderRefId());
        // 更新eip表
        floatingip.setFixedPortId(params.getInstanceId());
        floatingip.setInstanceId(netcard.getDeviceId());
        floatingip.setStatus(ResourceStates.status(ResourceType.FLOATING_IP, ProviderType.NEUTRON, State.ACTIVE.toString()));
        floatingipDao.update(floatingip);
    }

    @Override
    public void unAssociateEipAddress(String allocationId) {
        FloatingIp floatingip = floatingipDao.getById(allocationId);
        if (floatingip == null) {
            throw new GCloudException("0050302::弹性公网ip不存在");
        }
        // 更新eip表
        List<String> updatedField = new ArrayList<String>();
        updatedField.add(floatingip.updateFixedPortId(null));
        updatedField.add(floatingip.updateInstanceId(null));
        updatedField.add(floatingip.updateStatus(ResourceStates.status(ResourceType.FLOATING_IP, ProviderType.NEUTRON, State.DOWN.toString())));
        
        floatingipDao.update(floatingip, updatedField);
        this.checkAndGetProvider(floatingip.getProvider()).unAssociateEipAddress(floatingip.getProviderRefId());
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void releaseEipAddress(String allocationId) {
        FloatingIp floatingip = floatingipDao.getById(allocationId);
        if (floatingip == null) {
            throw new GCloudException("0050402::弹性公网ip不存在");
        }
        Port port = portDao.findUniqueByProperty(Port.DEVICE_ID, allocationId);
        if(port == null) {
            throw new GCloudException("0050403::port不存在");
        }

        List<QosFipPolicyBinding> bindings = qosFipPolicyBindingDao.findByProperty(QosFipPolicyBinding.FIP_ID, floatingip.getId());
        deleteFloatingIp(floatingip, port);
        if(bindings != null && bindings.size() > 0){
            for(QosFipPolicyBinding binding : bindings){
                try{
                    qosPolicyService.delete(binding.getPolicyId());
                }catch (Exception ex){
                    log.error(String.format("删除port qos policy 失败。policy id = %s, ex = %s", binding.getPolicyId(), ex), ex);
                }

            }
        }

    }

    private void deleteFloatingIp(FloatingIp floatingip, Port port){

        // 更新eip表
        floatingipDao.deleteById(floatingip.getId());
        qosFipPolicyBindingDao.deleteByFipId(floatingip.getId());
        portService.cleanPortData(port.getId());
        this.checkAndGetProvider(floatingip.getProvider()).releaseEipAddress(floatingip.getProviderRefId());
    }

    @Override
    public void modifyEipAddressAttribute(ModifyEipAddressAttributeParams param) {
        FloatingIp floatingip = floatingipDao.getById(param.getAllocationId());
        if (floatingip == null) {
            throw new GCloudException("::弹性公网ip不存在");
        }

        updateFipQosLimit(floatingip.getId(), param.getBandwidth() * 1024, param.getBandwidth() * 1024);
    }

    private void updateFipQosLimit(String fipId, Integer egress, Integer ingress){
        FloatingIp fip = floatingipDao.getById(fipId);
        //是否已经有策略
        //neutron port 只支持绑定一个策略,如果升级neutron版本后，支持多个策略，则需要修改逻辑
        QosFipPolicyBinding binding = qosFipPolicyBindingDao.findUniqueByProperty(QosFipPolicyBinding.FIP_ID, fipId);
        if(binding == null){
            createQosLimit(fip, egress, ingress);
        }else{
            qosPolicyService.updateQosLimit(binding.getPolicyId(), egress, ingress);
        }
    }

    private void createQosLimit(FloatingIp fip, Integer egress, Integer ingress){

        SimpleFlowChain<QosPolicy, String> chain = new SimpleFlowChain<>("create qos limit");
        chain.then(new Flow<QosPolicy>("create qos policy") {
            @Override
            public void run(SimpleFlowChain chain, QosPolicy data) {
                QosPolicy qosPolicy = qosPolicyService.createQosLimit(fip.getProvider(), egress, ingress);
                chain.data(qosPolicy);
                chain.next();
            }
            @Override
            public void rollback(SimpleFlowChain chain, QosPolicy data) {
                qosPolicyService.delete(data.getId());
                chain.rollback();
            }

            //创建规则的时候不会滚，直接又会policy回滚是删除
        }).then(new NoRollbackFlow<QosPolicy>("bind port and qos policy") {
            @Override
            public void run(SimpleFlowChain chain, QosPolicy data) {
                qosFipPolicyBindingService.bind(fip.getId(), data.getId());
                chain.next();
            }
        }).start();

        if(StringUtils.isNotBlank(chain.getErrorCode())){
            throw new GCloudException(chain.getErrorCode());
        }
    }

    private IFloatingIpProvider getProviderOrDefault() {
        IFloatingIpProvider provider = ResourceProviders.getDefault(ResourceType.FLOATING_IP);
        return provider;
    }

    private IFloatingIpProvider checkAndGetProvider(Integer providerType) {
        IFloatingIpProvider provider = ResourceProviders.checkAndGet(ResourceType.FLOATING_IP, providerType);
        return provider;
    }

	@Override
	public DetailEipAddressResponse detail(ApiDetailEipAddressMsg msg) {
		DescribeEipAddressesMsg param = new DescribeEipAddressesMsg();
		param.setCurrentUser(msg.getCurrentUser());
		param.setAllocationId(msg.getAllocationId());
		
		PageResult<EipAddressSetType> pages = floatingipDao.getByPage(param);
        if (pages.getTotalCount() == 0 ) {
            throw new GCloudException("0050602::弹性公网ip不存在");
        }
        EipAddressSetType eip = pages.getList().get(0);
        DetailEipAddressResponse res = new DetailEipAddressResponse();
        res.setAllocationId(msg.getAllocationId());
        res.setAllocationTime(eip.getAllocationTime());
        res.setBandwidth(eip.getBandwidth());
        res.setExternalNetworkId(eip.getExternalNetworkId());
        res.setInstanceId(eip.getInstanceId());
        res.setInstanceType(eip.getInstanceType());
        res.setIpAddress(eip.getIpAddress());
        res.setRegionId(eip.getRegionId());
        res.setStatus(eip.getStatus());
        res.setCnStatus(FloatingIpStatus.getCnName(eip.getStatus()));
		return res;
	}

	@Override
	public ApiEipAddressStatisticsReplyMsg statistic(CurrentUser currentUser) {
		ApiEipAddressStatisticsReplyMsg reply = new ApiEipAddressStatisticsReplyMsg();
		reply.setAllNum(floatingipDao.getAllNum(currentUser));
		return reply;
	}

}
